package ua.edu.nuos.jeetraining2012.cms.service;

import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.dto.GoogleCalendarEventDTO;
import ua.edu.nuos.jeetraining2012.cms.entity.CourseEntity;
import ua.edu.nuos.jeetraining2012.cms.entity.CourseMaterialEntity;
import ua.edu.nuos.jeetraining2012.cms.model.CourseDAO;
import ua.edu.nuos.jeetraining2012.cms.model.MaterialDAO;
import ua.edu.nuos.jeetraining2012.cms.model.exception.DAOException;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceException;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceValidationException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.auth.GoogleAuthException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.calendar.GoogleCalendarCreatingException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.transport.GoogleTransportException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.services.calendar.GoogleCalendar;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.validation.ConstraintViolation;
import javax.validation.ValidatorFactory;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * Material.
 * <p/>
 * Business logic for Course materials. Available methods are:
 *
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 02.06.13 Time: 19:10
 * @since 1.0
 */
@Stateless
@Local
public class Material {

    /**
     * Define DAO layer for Materials.
     */
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @EJB
    private MaterialDAO dao;

    /**
     * Define DAO layer for Courses.
     */
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @EJB
    private CourseDAO courseDAO;
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @Resource
    private ValidatorFactory validatorFactory;


    /**
     * Define logger.
     */
    final private static Logger logger = org.slf4j.LoggerFactory.getLogger(Material.class);

    /**
     * getMaterial().
     * <p/>
     * Use this method to get material by its ID.
     *
     * @param id                                material ID.
     * @return                                  Instance of {@link CourseMaterialEntity} or null.
     * @throws IllegalArgumentException         Thrown if illegal id sent.
     */
    public CourseMaterialEntity getMaterial(Long id) throws ServiceException, IllegalArgumentException {
        logger.debug("getMaterial() - started.");

        if (id == null || id < 1) {
            logger.error("getMaterial() Illegal id: {}. Can't be null or less then 1", id);
            logger.debug("getMaterial() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Material ID can't be null or less then 1.");
        }

        CourseMaterialEntity material = null;
        logger.debug("getMaterial() Trying to find material with id: {}", id);
        try {
            material = dao.findMaterialById(id);
            logger.debug("getMaterial() Material: {}", material.toString());
        } catch (DAOException e) {
            logger.error("getMaterial() Material can't be found. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("getMaterial() - finished with DAOException.");
        }

        if (material == null || material.getId() == 0) {
            logger.debug("getMaterial() Material is empty. Filling with default values.");
            material = fillMaterialWithDefaultValues();
        }

        logger.debug("getMaterial() Returned material is {}", material.toString());

        logger.debug("getMaterial() - finished.");
        return material;
    }

    /**
     * Retrieve course information.
     * <p/>
     * In case, when course info can't be retrieved.
     * Fill values with dummy values.
     *
     * @param id                            Course ID.
     * @return                              Course Title, Course start date, Course finish date.
     * @throws IllegalArgumentException     Thrown in case of course ID null or less then 1.
     * @throws ServiceException             Thrown if parent DAOException occurred.
     */
    public CourseEntity getCourseDescription(Long id) throws IllegalArgumentException, ServiceException {
        logger.debug("getCourseDescription() - started.");

        if (id == null || id < 1) {
            logger.error("Id can't be {}", id);
            logger.debug("getCourseDescription() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Id can't be null or less then 1");
        }

        CourseEntity course = null;
        logger.debug("Trying to find course info with id {}", id);
        try {
            course = courseDAO.findById(id);
            logger.debug("Found course: {}", course);
        } catch (Exception e) {
            logger.error("Can't get Course info. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("getCourseDescription() - finished with DAOException.");
            throw new ServiceException("Got parent DAOException with message: " + e.getMessage());
        }

        if (course == null) {
            course = new CourseEntity();
            course.setId(1);
            course.setTitle("Introduction to JavaEE");
            course.setDescription("A long-long description");
            course.setShortDescription("Brief desc");
            course.setStartDate(java.sql.Date.valueOf("2013-08-01"));
            course.setFinishDate(java.sql.Date.valueOf("2013-08-01"));
            course.setSyllabusLink("http://example.com/syllabus.pdf");
            course.setLogoURL("http://placehold.it/340x250");
        }

        logger.debug("getCourseDescription() - finished.");
        return course;
    }

    /**
     * Return course materials titles.
     * In case of success, course titles will be returned as:
     * <table border=1>
     *     <thead>
     *         <th>section_title</th>
     *         <th>title</th>
     *     </thead>
     *     <tbody>
     *         <td>section title example</td>
     *         <td>title example</td>
     *     </tbody>
     * </table>
     * If nothing were found, null will be returned.
     *
     * @param courseId                      Course ID.
     * @return                              List of titles or null.
     * @throws IllegalArgumentException     If course ID is null or less then 1.
     * @throws ServiceException             If parent DAOException was thrown.
     */
    public List<CourseMaterialEntity> getTitles(Long courseId) throws IllegalArgumentException, ServiceException {
        logger.debug("getTitles() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("courseId can't be {}.", courseId);
            logger.debug("getTitles() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course Id can't be null or less then 1.");
        }

        List<CourseMaterialEntity> titles = null;
        logger.debug("Trying to find out titles.");
        try {
            titles = dao.findTitlesByCourseId(courseId);
            logger.debug("Titles: {}", titles);
        } catch (DAOException e) {
             logger.error("Titles can't be retrieved because of error. Error message: {}, \nTrace:\n {}", e.getMessage(),
                     Arrays.deepToString(e.getStackTrace()));
            logger.debug("getTitles() - finished with ServiceException.");
            throw new ServiceException("Got parent DAOException with message: " + e.getMessage());
        }

        logger.debug("getTitles() - finished.");
        return titles;
    }

    /**
     * Get section titles.
     * <p/>
     * Return list of section titles for course with id courseId.
     *
     * @param courseId                      Course ID.
     * @return                              List of available section titles.
     * @throws IllegalArgumentException     If course ID null or less than 1.
     * @throws ServiceException             In case, when DAOException appeared.
     */
    public List<String> getSectionTitles(Long courseId) throws IllegalArgumentException, ServiceException {
        logger.debug("getSectionTitles() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("courseId can't be {}", courseId);
            logger.debug("getSectionTitles() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("Course ID can't be null or less than 1.");
        }

        List<String> sectionTitles = null;
        logger.debug("Trying to find titles for course with ID {}", courseId);

        try {
            sectionTitles = dao.findSectionTitles(courseId);
            logger.info("Titles were found: {}", sectionTitles);
        } catch (DAOException e) {
            logger.error("getSectionTitles() Titles can't be retrieved. Got DAOException with message: {} \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("getSectionTitles() finished with ServiceException.");
            throw new ServiceException("Can't retrieve section titles because of error: " + e.getMessage());
        }

        logger.debug("getSectionTitles() - finished.");
        return sectionTitles;
    }

    /**
     * getAllMaterials().
     * <p/>
     * Fetch all materials according to sent courseId.
     * In case of success List of {@link CourseMaterialEntity} will be returned, else null will be.
     * In cases, when courseId is null or less then 1, IllegalArgumentException will be thrown.
     * In cases, when parent dao layer thrown {@link DAOException}, {@link ServiceException} will be
     * thrown.
     *
     * @param courseId                          Long course ID.
     * @return                                  List of CourseMaterialEntity objects.
     * @throws IllegalArgumentException         Will thrown in cases, when illegal course id sent.
     * @throws ServiceException                 Will thrown in cases, when parent DAOException appeared.
     */
    public List<CourseMaterialEntity> getAllMaterials(Long courseId) throws IllegalArgumentException, ServiceException {
        logger.debug("getAllMaterials() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("getAllMaterials() Course ID is {}", courseId);
            logger.debug("getAllMaterials() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("Course ID can't be null or less then 1.");
        }

        List<CourseMaterialEntity> materials = null;

        logger.debug("getAllMaterials() Trying to fetch all materials from DB with courseId {}", courseId);
        try {
            materials = dao.findAllMaterialsByCourse(courseId);
            logger.debug("getAllMaterials() Fetched materials size:", materials.size());
        } catch (DAOException e) {
            logger.error("getAllMaterials() Materials can't be retrieved. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("getAllMaterials() - finished with ServiceException");
            throw new ServiceException("Materials can't be retrieved. Error message: " + e.getMessage());
        }

        logger.debug("getAllMaterials() - finished.");
        return materials;
    }

    /**
     * Populate CourseMaterialEntity with default values.
     * This method can be useful in cases, when real materials can't be retrieved from DB.
     *
     * @return          Instance of CourseMaterialEntity.
     */
    public CourseMaterialEntity fillMaterialWithDefaultValues() {
        logger.debug("fillMaterialWithDefaultValues() - started.");
        CourseMaterialEntity materialInfo = new CourseMaterialEntity();

        materialInfo.setId(0);
        materialInfo.setTitle("Material Title");
        materialInfo.setSectionTitle("Section Title");
        materialInfo.setStartDate(new Date());
        materialInfo.setDueDate(new Date());
        materialInfo.setShortDescription("Description");
        materialInfo.setText(null);
        materialInfo.setVideoURL(null);
        materialInfo.setSlideURL(null);

        materialInfo.setCourse(new CourseEntity());

        logger.debug("fillMaterialWithDefaultValues() MaterialInfo filled with values {}", materialInfo.toString());
        logger.debug("fillMaterialWithDefaultValues() - finished.");
        return materialInfo;
    }

    /**
     * Constructs and return valid empty entity object
     *
     * @return Empty valid CourseMaterialEntity object
     */
    public CourseMaterialEntity getEmptyEntity() {
        CourseMaterialEntity entity = new CourseMaterialEntity();
        String str = new String();
        entity.setId(0);
        entity.setTitle(str);
        entity.setSectionTitle(str);
        entity.setStartDate(new Date());
        entity.setDueDate(new Date());
        entity.setShortDescription(str);
        entity.setText(str);
        entity.setVideoURL(str);
        entity.setSlideURL(str);
        return entity;
    }

    /**
     * Persist material in storage. If this is new material entry it will be added, otherwise existed material entry is updated
     *
     * @param material CourseMaterialEntity to be persisted
     * @throws ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceValidationException
     *                                  When CourseMaterialEntity is invalid
     * @throws IllegalArgumentException When CourseMaterialEntity is null
     * @throws ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceException         When underlying storage throw an exception
     */
    public void persist(CourseMaterialEntity material) throws ServiceValidationException, ServiceException {
        logger.debug("persist() - started.");
        if (material == null)
            throw new IllegalArgumentException("DTO can't be null");

        Set<ConstraintViolation<CourseMaterialEntity>> validationResult = validatorFactory.getValidator().validate(material);
        if (!validationResult.isEmpty()) {
            throw new ServiceValidationException(validationResult);
        }
        boolean exist = isExist(material);
        try {
            if (exist) {
                dao.update(material);
            } else {
                dao.create(material);

                GoogleCalendarEventDTO eventDTO = new GoogleCalendarEventDTO();
                eventDTO.setEventTitle(material.getTitle());
                eventDTO.setEventStart(material.getStartDate());
                eventDTO.setEventEnd(material.getDueDate());
                eventDTO.setCalendarId(material.getCourse().getCalendarId());
                eventDTO.setEventDescription(material.getShortDescription());

                logger.debug("Saving event with params: {}", eventDTO);

                com.google.api.services.calendar.model.Event result =
                        GoogleCalendar.GCalendarEvents.create(eventDTO);

                if (result != null) {
                    material.setEventId(result.getId());
                }

                dao.update(material);       // Update material's event ID.
            }
        } catch (DAOException e) {
            logger.error("persist() - Received DAO Exception: {}", e.getMessage());
            throw new ServiceException("persist() - Received DAO Exception: " + e.getMessage());
        } catch (GoogleCalendarCreatingException e) {
            logger.error("persist() - can't create event for material. Error: {}", e.getMessage());
        } catch (GoogleAuthException e) {
            logger.error("persist() - can't authorize on Google service. Error: {}", e.getMessage());
        } catch (GoogleTransportException e) {
            logger.error("persist() - can't establish connection with Google service. Error: {}", e.getMessage());
        }
    }

    /**
     * Check if material is registered in storage by using material id from DTO.
     * Return true if material is registered, false otherwise
     *
     * @param material UserEntity id to be searched for
     * @return true if user is registered, false otherwise
     * @throws IllegalArgumentException When UserEntity is null
     * @throws ServiceException         When underlying storage throw an exception
     */
    public boolean isExist(CourseMaterialEntity material) throws ServiceException {
        if (material == null) {
            throw new IllegalArgumentException("Material entity can't be null");
        }
        return ((material.getId() != 0) && getMaterial(material.getId()) != null);
    }


    /**
     * Deletes material from the storage by using material id from DTO.
     * Return true if user is deleted,false otherwise.
     *
     * @param material DTO with material id
     * @return true if deleted, false otherwise
     * @throws ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceValidationException
     *                          When CourseMaterialEntity id is invalid
     * @throws ServiceException When underlying storage throw an exception
     */
    public boolean delete(CourseMaterialEntity material) throws ServiceValidationException, ServiceException {
        if (material == null)
            throw new IllegalArgumentException("DTO can't be null");

        if (!isExist(material))
            return false;

        try {
            return dao.delete(material);
        } catch (DAOException e) {
            logger.error("delete() received DAO Exception", e);
            throw new ServiceException("delete() received DAO Exception: " + e.getMessage());
        }
    }



}
